package com.example.demo.interceptor;

import cn.hutool.json.JSONUtil;
import com.example.demo.utils.IpUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;


import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.UUID;



/**
 * @author lyc
 * @link https://www.cnblogs.com/bigben0123/p/7779357.html
 *
 * 我们需要定义每个切面的优先级，我们需要@Order(i)注解来标识切面的优先级。i的值越小，优先级越高
 * @date 2019/6/6.
 */
@Aspect
@Component
public class WebLogHandler {

    private Logger logger = LoggerFactory.getLogger(WebLogHandler.class);

    @Pointcut("execution(public * com.example.demo.web..*.*(..))")
    public void webLog(){}


   /* @Before("webLog()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        System.out.println("URL : " + request.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + request.getMethod());
        System.out.println("IP : " + request.getRemoteAddr());
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));

    }

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求，返回内容
        System.out.println("方法的返回值 : " + ret);
    }

    //后置异常通知
    @AfterThrowing("webLog()")
    public void throwss(JoinPoint jp){
        System.out.println("方法异常时执行.....");
    }


    //后置最终通知,final增强，不管是抛出异常或者正常退出都会执行
    @After("webLog()")
    public void after(JoinPoint jp){
        System.out.println("方法最后执行.....");
    }*/


   public static final String TRACEID = "traceId";
    /**
     *   环绕通知,环绕增强，相当于MethodInterceptor
     *   可以考虑 日志保存 在数据库或者 其他容器 或者发给消息系统
     *   1.系统分开部署 可以考虑 使用 traceId 一条（我这里简单UUID 实现） 完整的结果 id 唯一 这里还可以带上 用户信息
     *     可以考虑 成熟 开源 zipkin 或者 SkyWalking
     *   2 .还可以监控 url 访问次数 （这里我就不具体实现了）比如直接聚合 那些url 耗时较长
     *
     *   3. 还可以做 ip 黑名单 等
     *
     *
     *   // 接收到请求，记录请求内容
     ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
     HttpServletRequest request = attributes.getRequest();
     //获取请求头中的User-Agent
     UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
     //打印请求的内容
     startTime = System.currentTimeMillis();
     log.info("请求Url : {}" , request.getRequestURL().toString());
     log.info("请求方式 : {}" , request.getMethod());
     log.info("请求ip : {}" , request.getRemoteAddr());
     log.info("请求方法 : " , pjp.getSignature().getDeclaringTypeName() , "." , pjp.getSignature().getName());
     log.info("请求参数 : {}" , Arrays.toString(pjp.getArgs()));
     // 系统信息
     log.info("浏览器：{}", userAgent.getBrowser().toString());
     log.info("浏览器版本：{}",userAgent.getBrowserVersion());
     log.info("操作系统: {}", userAgent.getOperatingSystem().toString());
     // pjp.proceed()：当我们执行完切面代码之后，还有继续处理业务相关的代码。proceed()方法会继续执行业务代码，并且其返回值，就是业务处理完成之后的返回值。
     Object ret = pjp.proceed();
     log.info("请求结束时间："+ LocalDateTime.now());
     log.info("请求耗时：{}" , (System.currentTimeMillis() - startTime));
     // 处理完请求，返回内容
     log.info("请求返回 : " , ret);
     */
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint pjp) throws Throwable {
        logger.info("方法环绕start.....");
        Long start = System.currentTimeMillis();
        try {
            // 接收到请求，记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            String traceId =  UUID.randomUUID().toString();
            //方便查找那台服务器报错
            InetAddress addr = InetAddress.getLocalHost();
            String ip = addr.getHostAddress().toString();
            String hostName = addr.getHostName().toString();
            //错误结果处理可以后续请求拿到
            request.setAttribute(TRACEID,traceId);
            // 记录下请求内容
            logger.info("traceId : {}" , traceId);

            logger.info("host : {} id {}" ,hostName,ip );
            logger.info("contextPath : {}" , request.getContextPath());
            logger.info("servletPath : {}" , request.getServletPath());
            logger.info("url : {}" ,request.getRequestURL());
            logger.info("uri : {}" ,request.getRequestURI());
            logger.info("http_method : {}" , request.getMethod());
            logger.info("IP:{}" , IpUtils.getIpAddr(request));
            logger.info("class_method : {},{}" , pjp.getSignature().getDeclaringTypeName() ,pjp.getSignature().getName());
            Object[] args = pjp.getArgs();
            //序列化目的反正对象里面数组打印原始hash 值 看不清楚
            logger.info("args : {},序列化数据：{}" , StringUtils.join(args,","),JSONUtil.parse(args));
            Object o =  pjp.proceed();
            logger.info("方法环绕proceed，正常结果response :" + o);
            return o;
        } catch (Throwable e) {
            //注意由于全局异常处理 这里需要抛出，不然全局异常会认为 方法没有出错
            logger.info("异常通知.....");
            throw e;
        }finally {
            Long end = System.currentTimeMillis();
            logger.info("最后通知.....耗时:{}秒",(end-start)/1000);
        }
    }
}